import io
import xlrd
import base64
import calendar
from itertools import groupby
from datetime import datetime, date, timedelta

from odoo import fields, models, _
from odoo.exceptions import ValidationError
from odoo.tools import float_compare, float_round, float_is_zero


class ImportPurchaseStatementWizard(models.TransientModel):
    _name = 's.import.purchase.statement.wizard'

    file = fields.Binary('File')

    def _get_month_end_date(self, date_value):
        """获取指定日期的月末日期"""
        if isinstance(date_value, str):
            date_value = datetime.strptime(date_value, '%Y-%m-%d')
        last_day = calendar.monthrange(date_value.year, date_value.month)[1]
        return date_value.replace(day=last_day)

    def action_import(self):
        def get_last_day_of_month(year, month):
            if month == 12:
                return date(year, month, 31)
            else:
                return date(year, month + 1, 1) - timedelta(days=1)

        if not self.file:
            raise ValidationError(_(''))

        file_content = base64.b64decode(self.file)

        workbook = xlrd.open_workbook(file_contents=file_content)

        sheet = workbook.sheet_by_index(0)

        vals = {}
        tax_invoice_number_vals = {}
        move_qty_vals = {}
        move_ids = self.env['stock.move']
        for row_num in range(sheet.nrows):
            if row_num < 1:
                continue

            row_data = sheet.row_values(row_num)
            if isinstance(row_data[0], str):
                continue
            stock_move_id = int(row_data[0])
            tax_invoice_number = row_data[16]
            if not tax_invoice_number:
                raise ValidationError(_('Missing tax invoice number'))

            if stock_move_id in tax_invoice_number_vals:
                tax_invoice_number_vals[stock_move_id].append(tax_invoice_number)
            else:
                tax_invoice_number_vals[stock_move_id] = [tax_invoice_number]

            if stock_move_id in vals:
                vals[stock_move_id] += row_data[8]
            else:
                vals[stock_move_id] = row_data[8]


        stock_move_line_ids = self.env['stock.move.line'].browse(list(vals.keys()))

        for m_line in stock_move_line_ids:
            move_id = m_line.move_id
            prepare_qty = vals.get(m_line.id, 0)
            # prepare_qty = m_line.product_uom_id._compute_quantity(prepare_qty, move_id.product_uom)
            remaining_qty = float_round(move_id.quantity - move_id.s_qty_invoiced, precision_rounding=move_id.product_uom.rounding)
            if float_is_zero(prepare_qty, precision_rounding=move_id.product_uom.rounding):
                vals.pop(move_id.id, 0)
                continue
            if float_compare(prepare_qty, remaining_qty, precision_rounding=move_id.product_uom.rounding) == 1:
                raise ValidationError(_('product %s, picking order %s, The reconciliation quantity %f is greater than the remaining reconcilable quantity %f', move_id.product_id.name, move_id.picking_id.name, prepare_qty, remaining_qty))

            move_ids |= move_id
            if move_id.id in move_qty_vals:
                move_qty_vals[move_id.id] += prepare_qty
            else:
                move_qty_vals[move_id.id] = prepare_qty

        precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
        invoice_vals_list = []
        sequence = 10

        move_ids = move_ids.filtered(lambda x: x.purchase_line_id)
        move_ids = move_ids.sorted(lambda x: (x.purchase_line_id.order_id.company_id.id, x.purchase_line_id.order_id.partner_id.id, x.purchase_line_id.order_id.currency_id))
        for group_key, g in groupby(move_ids, key=lambda x: (x.purchase_line_id.order_id.company_id.id, x.purchase_line_id.order_id.partner_id.id, x.purchase_line_id.order_id.currency_id)):
            g_move_ids = self.env['stock.move'].concat(*g)
            purchase_id = g_move_ids[0].purchase_line_id.order_id
            purchase_id = purchase_id.with_company(purchase_id.company_id)
            pending_section = None
            invoice_vals = purchase_id._prepare_invoice()
            for g_move in g_move_ids:
                if g_move.purchase_line_id.display_type == 'line_section':
                    pending_section = g_move
                    continue
                g_move_prepare_qty = move_qty_vals.get(g_move.id, 0)
                if not float_is_zero(g_move_prepare_qty, precision_digits=precision):
                    if pending_section:
                        line_vals = pending_section._prepare_account_move_line()
                        line_vals.update({'sequence': sequence})
                        invoice_vals['invoice_line_ids'].append((0, 0, line_vals))
                        sequence += 1
                        pending_section = None
                    line_vals = g_move.purchase_line_id._prepare_account_move_line()
                    # qty = g_move.product_uom._compute_quantity(g_move_prepare_qty, g_move.purchase_line_id.product_uom)
                    tax_invoice_number = tax_invoice_number_vals.get(g_move.id, [])
                    tax_invoice_number = list(set(tax_invoice_number))
                    if tax_invoice_number:
                        tax_invoice_number_str = ','.join(tax_invoice_number)
                    else:
                        tax_invoice_number_str = False
                    line_vals.update({
                        'sequence': sequence,
                        'quantity': g_move_prepare_qty,
                        's_stock_move_id': g_move.id,
                        's_tax_invoice_number': tax_invoice_number_str
                    })
                    invoice_vals['invoice_line_ids'].append((0, 0, line_vals))
                    sequence += 1
            start_date = min(g_move_ids.mapped('date'))
            end_date = self._get_month_end_date(start_date)
            if purchase_id.partner_id.bank_ids:
                bank = purchase_id.partner_id.bank_ids[0].id
            else:
                bank = False
            invoice_vals.update({
                's_statement_start_date': min(g_move_ids.mapped('date')),
                's_statement_end_date': end_date,
                'invoice_date': end_date,
                'partner_bank_id': bank
            })
            invoice_vals_list.append(invoice_vals)

        if not invoice_vals_list:
            raise ValidationError(_('There is no invoiceable line. If a product has a control policy based on received quantity, please make sure that a quantity has been received.'))

        new_invoice_vals_list = []
        for grouping_keys, invoices in groupby(invoice_vals_list, key=lambda x: (x.get('company_id'), x.get('partner_id'), x.get('currency_id'))):
            origins = set()
            payment_refs = set()
            refs = set()
            ref_invoice_vals = None
            for invoice_vals in invoices:
                if not ref_invoice_vals:
                    ref_invoice_vals = invoice_vals
                else:
                    ref_invoice_vals['invoice_line_ids'] += invoice_vals['invoice_line_ids']
                origins.add(invoice_vals['invoice_origin'])
                payment_refs.add(invoice_vals['payment_reference'])
                refs.add(invoice_vals['ref'])
            ref_invoice_vals.update({
                'ref': ', '.join(refs)[:2000],
                'invoice_origin': ', '.join(origins),
                'payment_reference': len(payment_refs) == 1 and payment_refs.pop() or False,
            })
            new_invoice_vals_list.append(ref_invoice_vals)
        invoice_vals_list = new_invoice_vals_list

        moves = self.env['account.move']
        AccountMove = self.env['account.move'].with_context(default_move_type='in_invoice')
        for vals in invoice_vals_list:
            moves |= AccountMove.with_company(vals['company_id']).create(vals)

        moves.filtered(lambda m: m.currency_id.round(m.amount_total) < 0).action_switch_move_type()

        return self.env['purchase.order'].action_view_invoice(moves)
